var NODE_UPDATE_SCALE_MAT = 1 ;// scale matrix is need update or not?
var NODE_UPDATE_ROTATE_MAT = 1 << 1;// rotate-----------------------------
var NODE_UPDATE_TRANSLATE_MAT = 1 << 2;// translate--------------------------
var NODE_PARENT_MAT_UPDATED = 1 << 3;// notice the parent's modelView matrix is changed!
var NODE_FRUSTUM_IN = 1 << 4;// notice this node is in view frustum
var NODE_FRUSTUM_TESTED = 1<<5;// this node is vfc tested or not
function gewNode(name)
{
  this.parent = null;
  this.parentOffset = 0;
  this.children = [];
  this.flags = NODE_UPDATE_SCALE_MAT | NODE_UPDATE_ROTATE_MAT | NODE_UPDATE_TRANSLATE_MAT;
  this.position = new gewVec3();
  this.scale = new gewVec3(1, 1, 1);
  this.rotate = new gewVec3();
  this.angle = 0; // degree
  this.scene3d = null;
  this.gl = null;
  this.game = null;
  this.materials = {};
  // Properties
  this.isVisible = true;
  this.isFrustumTest = true; // should do frustum culling with this node or not?
  this.isCalcModelMat = true; // should re-calc model Mat for this node or not?
  // Tranforms matrix
  this.translateMat = new gewMat4();
  this.scaleMat = new gewMat4();
  this.rotateMat = new gewMat4();
  this.modelMat = new gewMat4();
  this.localAabb = null; // aabb in local coordinate
  this.localSphere = null;
  this.worldAabb = new gewAabb(); // aabb in world coordinate
  this.worldSphere = new gewSphere();
  this.quadTreeCells = [];// quadtree cell list that this node's worldAabb is in ( for fast culling+collision dectection)
  this.quadTreeIdx = []; // idx of this node in cells ( for fast remove )
  
  // PRIVATE:
  if(name)
    this.name = name;
  else
    this.name = "Node"+getUniqueNumber();
    
  this.physicsBody = null; // is this node is a physicsBody?  
}
gewNode.prototype.destructor = function()
{
  var children = this.children;
  var childNum = children.length;
  for(var i=0;i<childNum;i++)
  {
    children[ children.length-1 ].destructor();
  }
  this.removeAllMaterials();
  var parent = this.parent;
  if (parent) {
    var idx = this.parentOffset;
    var parentChildren = parent.children;
    delete parentChildren[idx];
    parentChildren.splice(idx, 1);
    var parentChildNum = parentChildren.length;
    for (var i = idx; i < parentChildNum; i++) 
    {
      parentChildren[i].parentOffset--;
    }
  }
}
// This func is slow!
gewNode.prototype.getChildByName = function(name)
{
  var child = this.children;
  var num = child.length;
  for(var i=0;i<num;i++)
  {
    var node = child[i];
    if( node.name==name)
    {
      return node;
    }
    var result = node.getChildByName(name);
    if(result)
      return result;
  }
  return null;
}
gewNode.prototype.dup = function()
{
  var copy = new gewNode( this.name+"_"+getUniqueNumber());
  copy.copyFrom(this);
  return copy;
}
gewNode.prototype.copyFrom = function(des)
{ 
  des.parent.appendChild(this); 
  this.position.set(des.position.x,des.position.y,des.position.z);
  this.scale.set(des.scale.x,des.scale.y,des.scale.z);
  this.rotate.set(des.rotate.x,des.rotate.y,des.rotate.z);
  this.angle    = des.angle;
  var desChildren = des.children;
  for(var i=0;i<desChildren.length;i++)
  {
    var childCopy = desChildren[i].dup();
    this.appendChild(childCopy);
  }  
  for(var i in des.materials)
    this.addMaterial(des.materials[i]);
  // Properties
  this.isVisible = des.isVisible;
  this.isFrustumTest = des.isFrustumTest; // should do frustum culling with this node or not?
  this.isCalcModelMat = des.isCalcModelMat; // should re-calc model Mat for this node or not?
  
}
gewNode.prototype.setVisible=function(visible)
{
  this.isVisible = visible;
}
gewNode.prototype.getVisible=function()
{
  return this.isVisible;
}
gewNode.prototype.addMaterial = function(m)
{
  this.materials[m.name] = m;
  m.addNode(this);
}
gewNode.prototype.removeMaterial = function(m)
{
  var list = this.materials;
  for (var i in list) 
  {
    if (list[i] == m) 
    {
      delete list[i];
      break;
    }
  }
  m.removeNode(this);
}
gewNode.prototype.removeAllMaterials = function()
{
  var list = this.materials;
  for (var i in list) {
    list[i].removeNode(this);
  }
  delete this.materials;
  this.materials = {};
}
gewNode.prototype.getFlag = function(flag_bit)
{
  return (this.flags & flag_bit);
}
gewNode.prototype.setFlag = function(flag_bit, value, isSetAllSubTree)
{
  if (value == true) 
  {
    this.flags |= flag_bit;
  } else 
    if (value == false) 
    {
      this.flags &= (~ flag_bit);
    }
  if(isSetAllSubTree==true)
  {
    var children = this.children;
    var childNum = children.length;
    for(var i=0;i<childNum;i++)
    {
      children[i].setFlag(flag_bit,value,true);
    }
  }
}
gewNode.prototype.setPosition = function(pos)
{
  var tpos = this.position;
  tpos.x = pos.x;
  tpos.y = pos.y;
  tpos.z = pos.z;
  this.setFlag(NODE_UPDATE_TRANSLATE_MAT, true);
}
gewNode.prototype.setPositionXYZ = function(x, y, z)
{
  var tpos = this.position;
  tpos.x = x;
  tpos.y = y;
  tpos.z = z;
  this.setFlag(NODE_UPDATE_TRANSLATE_MAT, true);
}
gewNode.prototype.getPosition = function()
{
  return this.position.dup();
}
gewNode.prototype.setRotate = function(axis, angle)
{
  var trotate = this.rotate;
  trotate.x = axis.x;
  trotate.y = axis.y;
  trotate.z = axis.z;
  this.angle = angle;
  this.setFlag(NODE_UPDATE_ROTATE_MAT, true);
}
gewNode.prototype.setRotateXYZ = function(axis_x, axis_y, axis_z, angle)
{
  var trotate = this.rotate;
  trotate.x = axis_x;
  trotate.y = axis_y;
  trotate.z = axis_z;
  this.angle = angle;
  this.setFlag(NODE_UPDATE_ROTATE_MAT, true);
}
gewNode.prototype.getRotateAxis = function()
{
  return this.rotate.dup();
}
gewNode.prototype.getRotateAngle = function()
{
  return this.angle;
}
gewNode.prototype.setScale = function(scale)
{
  var tscale = this.scale;
  tscale.x = scale.x;
  tscale.y = scale.y;
  tscale.z = scale.z;
  this.setFlag(NODE_UPDATE_SCALE_MAT, true);
}
gewNode.prototype.setScaleXYZ = function(x, y, z)
{
  var tscale = this.scale;
  tscale.x = x;
  tscale.y = y;
  tscale.z = z;
  this.setFlag(NODE_UPDATE_SCALE_MAT, true);
}
gewNode.prototype.getScale = function()
{
  return this.scale.dup();
}
gewNode.prototype.appendChild = function(childNode)
{
  var children = this.children;
  var old_parent = childNode.parent;
  if (old_parent != null) 
  {
    old_parent.removeChild(childNode);
  }
  var offset = children.push(childNode);
  offset--;
  childNode.parentOffset = offset;
  childNode.parent = this;
  childNode.scene3d = this.scene3d;
  childNode.gl = this.gl;
  childNode.game = this.game;
  childNode.setFlag(NODE_PARENT_MAT_UPDATED, true);
  // Notify this node and it ancestor need to re-calculate tree bounding volume
  /*var tempNode = this;
  while (tempNode != null) 
  {
    tempNode.setFlag(NODE_UPDATE_TREE_BOUNDING_VOLUME, true);
    tempNode = tempNode.parent;
  }
  */
}
gewNode.prototype.removeChild = function(childNode)
{
  childNode.parent = null;
  childNode.removeAllMaterials();
  var offset = childNode.parentOffset;
  var children = this.children;
  children.splice(offset, 1);
  var childrenNum = children.length;
  for (var i = offset; i < childrenNum; i++) 
  {
    children[i].parentOffset--;
  }
  // Notify this node and it ancestor need to re-calculate tree bounding volume
  /*var tempNode = this;
  while (tempNode != null) 
  {
    tempNode.setFlag(NODE_UPDATE_TREE_BOUNDING_VOLUME, true);
    tempNode = tempNode.parent;
  }*/
}
gewNode.prototype.killChild = function(childNode)
{
  childNode.destructor();
}
gewNode.prototype.getModelMat = function()
{
  return this.modelMat.dup();
}
gewNode.prototype.prepareRender = function()
{
  // do animated
  // update model matrix 
  //if (this.getFlag(NODE_NO_TRANSFORM)) 
    //return;
  var needUpdateModelMatrixMask = NODE_UPDATE_ROTATE_MAT |
  NODE_UPDATE_SCALE_MAT |
  NODE_UPDATE_TRANSLATE_MAT |
  NODE_PARENT_MAT_UPDATED;
  if (this.flags & needUpdateModelMatrixMask) {
    if(this.isCalcModelMat)
      this.updateModelMat();
    // Re-calculate bounding volume
    this.updateWorldAabb();
    this.updateWorldSphere();
  }
  // Recusive to all chidren
  var children = this.children;
  var childrenNum = children.length;
  for (var i = 0; i < childrenNum; i++) 
  {
    children[i].prepareRender();
  }
}
gewNode.prototype.updateWorldAabb = function()
{
  var waabb = this.worldAabb;
  var aabb = this.localAabb;
  if (!aabb) 
    return;
  var m = this.modelMat;
  var mdata = m.data;
  var isRotation = mdata[1] || mdata[2] || mdata[4] || mdata[6] || mdata[8] || mdata[9];
  if (isRotation == 0) 
  {
    m.mulVec3(aabb.min,waabb.min);
    m.mulVec3(aabb.max,waabb.max);
    waabb.repair();
  } 
  else 
  {
    var aabbCorners = aabb.corners;
    var waabbCorners = waabb.corners;
    m.mulVec3(aabbCorners[0],waabbCorners[0]);
    m.mulVec3(aabbCorners[1],waabbCorners[1]);
    m.mulVec3(aabbCorners[2],waabbCorners[2]);
    m.mulVec3(aabbCorners[3],waabbCorners[3]);
    m.mulVec3(aabbCorners[4],waabbCorners[4]);
    m.mulVec3(aabbCorners[5],waabbCorners[5]);
    m.mulVec3(aabbCorners[6],waabbCorners[6]);
    m.mulVec3(aabbCorners[7],waabbCorners[7]);
    waabb.initFromCorner(waabbCorners);
  }
  
  var quadTree = this.scene3d.quadTree;
  // find new cell list
  var newCells = [];
  var currentCells = this.quadTreeCells;
  var minx = waabb.min.x;
  var maxx = waabb.max.x;
  var minz = waabb.min.z;
  var maxz = waabb.max.z;
  //var cell1 = quadTree.getCellAt(minx,minz);
  //var cell2 = quadTree.getCellAt(maxx,maxz);
  var r1 = quadTree.getRow(minx,minz);
  var c1 = quadTree.getColumn(minx,minz);
  
  var r2 = quadTree.getRow(maxx,maxz);
  var c2 = quadTree.getColumn(maxx,maxz);
  for(var r=r1;r<=r2;r++)
  for(var c=c1;c<=c2;c++)
  {
    newCells.push(quadTree.cell[c+r*quadTree.columnNum]);
  }
  /*
  newCells.push(cell1);
  var cell1r = quadTree.getRow(x,)
  if(cell2.id==cell1.id)
  {
  }
  else if( (cell2.id-cell1.id)< )
  else if(cell2.id==(cell1.id+quadTree.columnNum+1))
  {
    newCells.push(quadTree.cell[cell1.id+1]);
    newCells.push(quadTree.cell[cell2.id-1]);
    newCells.push(cell2);
  }
  else
  {
    newCells.push(cell2);
  }
  */
  // remove node from cell that in  currentCells and not in newCells
  var currentLen = currentCells.length;
  var newLen = newCells.length;
  for(var i=0;i<currentLen;i++)
  {
    if( !isInArray(currentCells[i],newCells) )
    {
      currentCells[i].removeNode(this,i);
      i--;currentLen--;
    }
  }
  // add node to cell that in newCells and not in current cells
  for(var i=0;i<newLen;i++)
  {
    if( !isInArray(newCells[i],currentCells))
      newCells[i].addNode(this);
  }  
}
gewNode.prototype.updateWorldSphere = function()
{
  var wsphere = this.worldSphere;
  var sphere = this.localSphere;
  if (!sphere) 
    return;
  var m = this.modelMat;
  wsphere.center = m.mulVec3(sphere.center);
  var scale_x = this.scale.x;
  var scale_y = this.scale.y;
  var scale_z = this.scale.z;
  wsphere.radius = sphere.radius*Math.max(scale_x, scale_y, scale_z);
  //this.treeSphere.copyFrom(wsphere);
}
/*gewNode.prototype.updateTreeBoundingVolume = function()
{
  var children = this.children;
  var childNum = children.length;
  if(this.getFlag(NODE_UPDATE_TREE_BOUNDING_VOLUME))
  {
    var treeBoundingBox = this.treeAabb;
    var treeBoundingSphere = this.treeSphere;
    var laabb = this.localAabb;
    var lsphere = this.localSphere;
    for(var i = 0; i < childNum; i++)
    {
      var child = children[i];
      child.updateTreeBoundingVolume();
      var box = child.treeAabb;
      var sphere = child.treeSphere;
      if( !box || !sphere)
        continue;
      if (laabb)
          treeBoundingBox.addInternalBox(box);
      else
          treeBoundingBox.copyFrom(box);
      if (lsphere)
          treeBoundingSphere.addInternalSphere(sphere);
      else
          treeBoundingSphere.copyFrom(sphere);
    }
  }    
}*/
gewNode.prototype.viewFrustumCulling = function(frustum)
{
  if(this.getFlag(NODE_FRUSTUM_TESTED))
    return;
  if(this.isFrustumTest)
  {
    this.setFlag(NODE_FRUSTUM_TESTED,true); 
    var waabb = this.worldAabb;
    var wsphere = this.worldSphere;
    var test1 = frustum.isContainSphere(wsphere);
    var test2,test3;
    switch(test1)
    {
      case GEW_IN:// this node is total inside frustum, dont need to check it children anymore
        this.setFlag(NODE_FRUSTUM_IN,true);
        return;
      case GEW_OUT:// this node is total out frustum
        return;
      case GEW_INTERSECT:
      case GEW_INTERNAL:
         // Check again using bounding box for more accuracy
        test2 = frustum.isContainAabb(waabb);
        switch (test2)
        {
          case GEW_IN:
          case GEW_INTERSECT:
          case GEW_INTERNAL:
              this.setFlag(NODE_FRUSTUM_IN,true);
              return;
          case GEW_OUT:
              return;
        }
    }
  }
}
     
  

  
gewNode.prototype.updateModelMat = function()
{
  if (this.getFlag(NODE_UPDATE_SCALE_MAT)) 
  {
    this.scaleMat = gewMat4.getScale(this.scale);
    //console.log("gewNode:Update scale mat");
  }
  if (this.getFlag(NODE_UPDATE_ROTATE_MAT)) 
  {
    var radian = this.angle * GEW_MATH_DEGREE_TO_RADIAN;
    this.rotateMat = gewMat4.getRotate(this.rotate, radian);
    //console.log("gewNode:Update rotate mat");
  }
  if (this.getFlag(NODE_UPDATE_TRANSLATE_MAT)) 
  {
    this.translateMat = gewMat4.getTranslate(this.position);
    //console.log("gewNode:Update translate mat");
  }
  var parentMat = this.parent.modelMat;
  var modelMat = this.modelMat;
  if (parentMat) 
    ((parentMat.mul(this.translateMat,modelMat)).mul(this.rotateMat,modelMat)).mul(this.scaleMat,modelMat);
  else 
    (this.translateMat.mul(this.rotateMat,modelMat)).mul(this.scaleMat,modelMat);
  //console.log("gewNode:Update model mat");
  // Nofity NODE_PARENT_MAT_UPDATED to all chidren
  var children = this.children;
  var childrenNum = children.length;
  for (var i = 0; i < childrenNum; i++) 
  {
    children[i].setFlag(NODE_PARENT_MAT_UPDATED, true);
  }  
}
gewNode.prototype.resetFlags = function(unclearMask)
{
  this.flags = this.flags & unclearMask;
  var children = this.children;
  var childrenNum = children.length;
  for (var i = 0; i < childrenNum; i++) 
  {
    children[i].resetFlags(unclearMask);
  }
}
//Debug draw
gewNode.prototype.getInFrustumCount = function()
{
  var count = 0;
  if(this.getFlag(NODE_FRUSTUM_IN))
    count++;
  var children = this.children;
  var childrenNum = children.length;
  for (var i = 0; i < childrenNum; i++) 
  {
    var childCount = children[i].getInFrustumCount();
    count+=childCount; 
  }
  return count;
}
gewNode.prototype.debugDraw = function()
{
  if(this.localAabb)
  {
    var scene3d = this.scene3d;  
    drawAabb(this.gl, scene3d.debugDrawer, this.worldAabb);
    //drawWireSphere(this.gl,scene3d.debugDrawer,this.worldSphere.center,this.worldSphere.radius,15,15 );
  }
  var children = this.children;
  var childrenNum = children.length;
  for (var i = 0; i < childrenNum; i++) 
  {
    children[i].debugDraw();
  }
}